#!/usr/bin/env python3

#==============================================================================
# Functionality
#==============================================================================
import pdb
import sys
import os
import re

# utility funcs, classes, etc go here.

def asserting(cond):
    if not cond:
        pdb.set_trace()
    assert(cond)

def has_stdin():
    return not sys.stdin.isatty()

def reg(pat, flags=0):
    return re.compile(pat, re.VERBOSE | flags)

#==============================================================================
# Cmdline
#==============================================================================
import argparse

parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, 
    description="""
TODO
""")

parser.add_argument('path',
    help="The DNS url. (Example: foo.bar.gpt4.org)" )

parser.add_argument('-u', '--update',
    help="The content of the record. (Example: 34.71.86.138)" )

parser.add_argument('-f', '--update-file',
    help="The content of the record will be set to the contents of this file. (Example: <(echo 34.71.86.138))" )

parser.add_argument('-d', '--delete',
    action="store_true",
    help="Delete the DNS entries." )

parser.add_argument('--dry-run',
    action="store_true",
    help="Don't modify any records." )
     
parser.add_argument('-v', '--verbose',
    action="store_true",
    help="verbose output" )
     
parser.add_argument('-k', '--kind',
    default=None,
    help="Record types to match. (Example: A,AAAA)" )

parser.add_argument('-t', '--ttl',
    default=1,
    help="When updating DNS, this is the TTL in seconds" )

parser.add_argument('-p', '--proxied',
    action='store_true',
    help="Whether to use cloudflare proxying for this DNS entry" )

args = None

#==============================================================================
# Main
#==============================================================================
from urllib.parse import urlparse
from pprint import pprint as pp
import glob
import json

from collections import namedtuple

import CloudFlare
cf = CloudFlare.CloudFlare()

DNS = namedtuple('DNS', 'zone_name name type content proxied ttl locked id zone_id'.split())

def as_dns(x):
  if not isinstance(x, (list, tuple)):
    return as_dns([x])[0]
  return [DNS(**{k: v for k, v in record.items() if k in DNS._fields}) for record in x]

def get_zone_name(path):
  if path is None:
    return None
  return '.'.join(path.rsplit('.', 2)[-2:])

def get_zones(path=None):
  results = []
  name = get_zone_name(path)
  for zone in cf.zones.get():
    if match(zone['name'], name):
      results.append(zone)
  return results

def match(x, pat=None):
  if pat is None:
    return x
  for part in pat.split(','):
    part = part.strip()
    if glob.fnmatch.fnmatch(x, part):
      return x

def get_dns_records(path=None, kind=None): # kind='A,AAAA'
  results = []
  for zone in get_zones(path=path):
    zone_id = zone['id']
    records = []
    page = 1
    while True:
      n = len(records)
      params = {'name':None, 'match':'all', 'type':kind, 'page':page, 'per_page': 50}
      records.extend(cf.zones.dns_records.get(zone_id, params=params))
      if len(records) == n:
        break
      page += 1
    records = [record for record in records if match(record['name'], path)]
    results.extend(records)
  return results

def set_dns_record(name, content, kind='A', proxied=False, ttl=1, dry_run=False):
    if '*' in name or ',' in name:
      raise ValueError("Invalid name: {!r}".format(name))
    if '*' in kind or ',' in kind or kind.upper() != kind:
      raise ValueError("Invalid kind: {!r}".format(kind))
    records = get_dns_records(name, kind=kind)
    if len(records) > 1:
      raise ValueError("Multiple DNS records matched name={} kind={}".format(name, kind))
    zone_id = get_zones(name)[0]['id']
    dns_record_id = None
    dns_record = {
        'name': name,
        'type': kind,
        'content': content,
        'proxied': proxied,
        'ttl': ttl,
        }
    for record in records:
      if record['content'] == content:
        return 'unmodified'
      dns_record_id = record['id']
      break
    if dns_record_id is None:
      if dry_run:
        print('cf.zones.dns_records.post(zone_id={!r}, data={!r})'.format(zone_id, dns_record), file=sys.stderr)
      else:
        cf.zones.dns_records.post(zone_id, data=dns_record)
      return 'created'
    else:
      if dry_run:
        print('cf.zones.dns_records.put(zone_id={!r}, dns_record_id={!r}, data={!r})'.format(zone_id, dns_record_id, dns_record), file=sys.stderr)
      else:
        cf.zones.dns_records.put(zone_id, dns_record_id, data=dns_record)
      return 'updated'

def run():
    if args.verbose:
        print(args, file=sys.stderr)
    if args.update_file is not None:
      if args.update_file == '-':
        args.update = sys.stdin.read()
      else:
        with open(args.update_file) as f:
          args.update = f.read()
    if args.update is not None:
      set_dns_record(args.path, args.update, kind=args.kind or 'A', proxied=args.proxied, ttl=args.ttl, dry_run=args.dry_run)
    records = get_dns_records(path=args.path, kind=args.kind)
    if args.delete:
      for record in records:
        if args.dry_run:
          print("cf.zones.dns_records.delete(zone_id={!r}, id={!r})".format(record['zone_id'], record['id']), file=sys.stderr)
        else:
          cf.zones.dns_records.delete(record['zone_id'], record['id'])
    for record in records:
      print(json.dumps(record))

def main():
    try:
        global args
        if not args:
            #args, leftovers = parser.parse_known_args()
            #args.args = leftovers
            args = parser.parse_args()
        return run()
    except IOError:
        # http://stackoverflow.com/questions/15793886/how-to-avoid-a-broken-pipe-error-when-printing-a-large-amount-of-formatted-data
        try:
            sys.stdout.close()
        except IOError:
            pass
        try:
            sys.stderr.close()
        except IOError:
            pass

if __name__ == "__main__":
    main()

